package name.cyrax.red.jpv.impl.adapters;

import java.lang.reflect.Array;

import name.cyrax.red.jpv.api.adapters.DoubleAdapter;
import name.cyrax.red.jpv.api.adapters.IntegerAdapter;
import name.cyrax.red.jpv.api.adapters.LongAdapter;
import name.cyrax.red.jpv.api.adapters.NumberAdapter;
import name.cyrax.red.jpv.api.adapters.NumericAccessor;
import name.cyrax.red.jpv.api.adapters.NumericAdapter;
import name.cyrax.red.jpv.api.adapters.OmniNumberAdapter;
import name.cyrax.red.jpv.api.funcs.FunctorInstance;
import name.cyrax.red.jpv.api.funcs.info.ValueType;
import name.cyrax.red.jpv.impl.funcs.DefaultFunctorInstances;
import name.cyrax.red.jpv.impl.funcs.MapReduce;

public final class NumericAdapters
{
	private static final OmniNumberAdapter<?> empty = new IntegerImpl() {		
		@Override public int size()	{
			return 0;
		}
		@Override public int getInteger(int index) {
			checkIndex(index);
			return 0;
		}
		@Override public NumericAdapter getParam(int index)	{
			return null;
		}
		@Override public FunctorInstance getFunctorInstance() {
			return getDefaultFunctorInstance();
		};
	};
	
	private NumericAdapters() {};
	
	@SuppressWarnings("unchecked")
	public static <N extends Number> OmniNumberAdapter<N> empty()
	{
		return (OmniNumberAdapter<N>) empty;
	}
	
	public static OmniNumberAdapter<Integer> newFill(final int size, final int v)
	{
		return new IntegerImpl() {			
			@Override public int getInteger(int index) {
				checkIndex(index);
				return v;
			}			
			@Override public int size() {
				return size;
			}			
			@Override public NumericAdapter getParam(int index)	{
				return null;
			}
			@Override public FunctorInstance getFunctorInstance() {
				return getDefaultFunctorInstance();
			};
		};
	}
	
	public static OmniNumberAdapter<Integer> newInstance(final int v)
	{
		return newFill(1, v);
	}

	public static OmniNumberAdapter<Integer> newInstance(final int... v)
	{
		return newInstance0(v.clone());
	}
	
	public static OmniNumberAdapter<Integer> copy(IntegerAdapter adapter)
	{
		return newInstance0(MapReduce.map(adapter));
	}
	
	public static OmniNumberAdapter<Long> copy(LongAdapter adapter)
	{
		return newInstance0(MapReduce.map(adapter));
	}
	
	public static OmniNumberAdapter<Double> copy(DoubleAdapter adapter)
	{
		return newInstance0(MapReduce.map(adapter));
	}
	
	static OmniNumberAdapter<Integer> newInstance0(final int[] v)
	{
		return new IntegerImpl() {			
			@Override public int getInteger(int index)	{
				checkIndex(index);
				return v[index];
			}			
			@Override public int size()	{
				return v.length;
			}
			@Override public NumericAdapter getParam(int index)	{
				return null;
			}
			@Override public FunctorInstance getFunctorInstance() {
				return DefaultFunctorInstances.ARRAY.getFunctorInstance(
						DefaultFunctorInstances.FUNCTOR_STUB_ADAPTER, v, ValueType.INTEGER);
			}
		};
	}
	
	static OmniNumberAdapter<Long> newInstance0(final long[] v)
	{
		return new LongImpl() {			
			@Override public long getLong(int index) {
				checkIndex(index);
				return v[index];
			}			
			@Override public int size() {
				return v.length;
			}
			@Override public NumericAdapter getParam(int index)	{
				return null;
			}
			@Override public FunctorInstance getFunctorInstance() {
				return DefaultFunctorInstances.ARRAY.getFunctorInstance(
						DefaultFunctorInstances.FUNCTOR_STUB_ADAPTER, v, ValueType.LONG);
			}
		};
	}
	
	static OmniNumberAdapter<Double> newInstance0(final double[] v)
	{
		return new DoubleImpl() {		
			@Override public double getDouble(int index) {
				checkIndex(index);
				return v[index];
			}			
			@Override public int size() {
				return v.length;
			}
			@Override public NumericAdapter getParam(int index)	{
				return null;
			}
			@Override public FunctorInstance getFunctorInstance() {
				return DefaultFunctorInstances.ARRAY.getFunctorInstance(
						DefaultFunctorInstances.FUNCTOR_STUB_ADAPTER, v, ValueType.DOUBLE);
			}
		};
	}
	
	private static String outOfBoundsMsg(int index, int size)
	{
        return "Index: " + index + ", Size: " + size;
	}
	
	public static void checkArraySize(Object a, int end) 
	{ 
		int s = Array.getLength(a);
		if (s < end)
			throw new IndexOutOfBoundsException(outOfBoundsMsg(end, s));
	}
	
	public static StringBuilder toString(NumberAdapter<?> adapter)
	{
		return toString(null, adapter);
	}
	
	public static StringBuilder toString(StringBuilder builder, final NumberAdapter<?> adapter)
	{
		final StringBuilder result = builder != null ? builder : new StringBuilder();
		result.append("[ ");
		
		adapter.detectType(new NumericAccessor<StringBuilder>() {

			@Override public StringBuilder asInteger(IntegerAdapter adapter)
			{
				for (int i = 0, s = adapter.size(); i < s; i++)
				{
					if (i > 0) result.append(", ");
					result.append(adapter.getInteger(i));
				}
				return result;
			}

			@Override public StringBuilder asLong(LongAdapter adapter)
			{
				for (int i = 0, s = adapter.size(); i < s; i++)
				{
					if (i > 0) result.append(", ");
					result.append(adapter.getLong(i));
				}
				return result;
			}

			@Override public StringBuilder asDouble(DoubleAdapter adapter)
			{
				for (int i = 0, s = adapter.size(); i < s; i++)
				{
					if (i > 0) result.append(", ");
					result.append(adapter.getDouble(i));
				}
				return result;
			}
		});
		result.append(" ]");
		return result;
	}

//	public static IntegerAdapter asInteger(IntegerAdapter v) {
//		return v;
//	}
//	
//	public static IntegerAdapter asInteger(final NumericAdapter v) {
//		if (v instanceof IntegerAdapter) return (IntegerAdapter) v;
//		
//		return v.detectType(new NumericAccessor<IntegerAdapter>() {
//			@Override public IntegerAdapter asInteger(IntegerAdapter adapter) {
//				return adapter;
//			}
//
//			@Override public IntegerAdapter asLong(final LongAdapter adapter) {
//				return new IntegerImpl() {
//					@Override public int getInteger(int index) {
//						return (int) adapter.getLong(index);
//					}					
//					@Override public int size() {
//						return adapter.size();
//					}
//					@Override public NumericAdapter getParam(int index) {
//						return switchOf(index, adapter); 
//					} 
//				};
//			}
//
//			@Override public IntegerAdapter asDouble(final DoubleAdapter adapter)
//			{
//				return new IntegerImpl() {
//					@Override public int getInteger(int index) {
//						return (int) adapter.getDouble(index);
//					}					
//					@Override public int size() {
//						return adapter.size();
//					}
//					@Override public NumericAdapter getParam(int index) {
//						return switchOf(index, adapter); 
//					} 
//				};
//			}
//		});
//	}
//
//	public static LongAdapter asLong(LongAdapter v) {
//		return v;
//	}
//	
//	public static LongAdapter asLong(final NumericAdapter v) {
//		if (v instanceof LongAdapter) return (LongAdapter) v;
//		
//		return v.detectType(new NumericAccessor<LongAdapter>() {
//			@Override public LongAdapter asInteger(final IntegerAdapter adapter) {
//				return new LongImpl() {
//					@Override public long getLong(int index) {
//						return adapter.getInteger(index);
//					}					
//					@Override public int size() {
//						return adapter.size();
//					}
//					@Override public NumericAdapter getParam(int index) {
//						return switchOf(index, adapter); 
//					}									
//				};
//			}
//
//			@Override public LongAdapter asLong(final LongAdapter adapter) {
//				return adapter;
//			}
//
//			@Override public LongAdapter asDouble(final DoubleAdapter adapter)
//			{
//				return new LongImpl() {
//					@Override public long getLong(int index) {
//						return (long) adapter.getDouble(index);
//					}					
//					@Override public int size() {
//						return adapter.size();
//					}
//					@Override public NumericAdapter getParam(int index) {
//						return switchOf(index, adapter); 
//					} 
//				};
//			}
//		});
//	}
//	
//	public static DoubleAdapter asDouble(DoubleAdapter v) {
//		return v;
//	}
//	
//	public static DoubleAdapter asDouble(final NumericAdapter v) {
//		if (v instanceof DoubleAdapter) return (DoubleAdapter) v;
//		
//		return v.detectType(new NumericAccessor<DoubleAdapter>() {
//			@Override public DoubleAdapter asInteger(final IntegerAdapter adapter) {
//				return new DoubleImpl() {
//					@Override public double getDouble(int index) {
//						return adapter.getInteger(index);
//					}					
//					@Override public int size() {
//						return adapter.size();
//					}
//					@Override public NumericAdapter getParam(int index) {
//						return switchOf(index, adapter); 
//					}									
//				};
//			}
//
//			@Override public DoubleAdapter asLong(final LongAdapter adapter)
//			{
//				return new DoubleImpl() {
//					@Override public double getDouble(int index) {
//						return adapter.getLong(index);
//					}					
//					@Override public int size() {
//						return adapter.size();
//					}
//					@Override public NumericAdapter getParam(int index) {
//						return switchOf(index, adapter); 
//					}									
//				};
//			}
//
//			@Override public DoubleAdapter asDouble(final DoubleAdapter adapter) {
//				return adapter;
//			}
//
//		});
//	}
	
}

class NumericValue 
{
	private final Number value;
	private final NumericType type;
	
	public Number getValue()
	{
		return value;
	}

	public NumericType getType()
	{
		return type;
	}

	public NumericValue(Number value, NumericType type)
	{
		super();
		this.value = value;
		this.type = type;
	}

	@Override public int hashCode()
	{
		final int prime = 31;
		int result = 1;
		result = prime * result + ((type == null) ? 0 : type.hashCode());
		result = prime * result + ((value == null) ? 0 : value.hashCode());
		return result;
	}

	@Override public boolean equals(Object obj)
	{
		if (this == obj) return true;
		if (obj == null) return false;
		if (getClass() != obj.getClass()) return false;
		NumericValue other = (NumericValue) obj;
		if (type != other.type) return false;
		if (value == null)
		{
			if (other.value != null) return false;
		} else if (!value.equals(other.value)) return false;
		return true;
	}
}